import Glibc

class Timer {
    private let CLOCK_REALTIME = 0
    private var start_timespec = timespec()
    private var end_timespec = timespec()
    private var time_spec = timespec()
    func start() {
        clock_gettime(Int32(CLOCK_REALTIME),&start_timespec)
    }
    
    func stop() -> Double {
        clock_gettime(Int32(CLOCK_REALTIME),&end_timespec)
        let start_time = Double(start_timespec.tv_sec * 1_000_000 + start_timespec.tv_nsec / 1_000)
        let end_time = Double(end_timespec.tv_sec * 1_000_000 + end_timespec.tv_nsec / 1_000)
        let time = end_time - start_time
        return time / 1_000
    }
    func getTime() -> Double {
        clock_gettime(Int32(CLOCK_REALTIME),&time_spec)
        return Double(time_spec.tv_sec * 1_000_000 + time_spec.tv_nsec / 1_000)
    }
}

let timer = Timer()
func createVector(x: Double, y: Double, z: Double) -> [Double] {
    return [x, y, z]
}

func lengthVector(_ self: [Double]) -> Double {
    return sqrt(self[0] * self[0] + self[1] * self[1] + self[2] * self[2])
}

func addVector(_ self: inout [Double], _ v: [Double]) -> [Double] {
    self[0] += v[0]
    self[1] += v[1]
    self[2] += v[2]
    return self
}

func subVector(_ self: inout [Double], _ v: [Double]) -> [Double] {
    self[0] -= v[0]
    self[1] -= v[1]
    self[2] -= v[2]
    return self
}

func scaleVector(_ self: inout [Double], _ scale: Double) -> [Double] {
    self[0] *= scale
    self[1] *= scale
    self[2] *= scale
    return self
}

func normaliseVector(_ self: inout [Double]) -> [Double] {
    let len = sqrt(self[0] * self[0] + self[1] * self[1] + self[2] * self[2])
    self[0] /= len
    self[1] /= len
    self[2] /= len
    return self
}

func add(_ v1: [Double], _ v2: [Double]) -> [Double] {
    return [v1[0] + v2[0], v1[1] + v2[1], v1[2] + v2[2]]
}

func sub(_ v1: [Double], _ v2: [Double]) -> [Double] {
    return [v1[0] - v2[0], v1[1] - v2[1], v1[2] - v2[2]]
}

func scalev(_ v1: [Double], _ v2: [Double]) -> [Double] {
    return [v1[0] * v2[0], v1[1] * v2[1], v1[2] * v2[2]]
}

func dot(_ v1: [Double], _ v2: [Double]) -> Double {
    return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]
}

func scale(_ v: inout [Double], _ scale: Double) -> [Double] {
    return [v[0] * scale, v[1] * scale, v[2] * scale]
}

func cross(_ v1: [Double], _ v2: [Double]) -> [Double] {
    return [v1[1] * v2[2] - v1[2] * v2[1],
            v1[2] * v2[0] - v1[0] * v2[2],
            v1[0] * v2[1] - v1[1] * v2[0]]
}

func normalise(_ v: [Double]) -> [Double] {
    let len = lengthVector(v)
    return [v[0] / len, v[1] / len, v[2] / len]
}

func transformMatrix(_ self: [Double], _ v: [Double]) -> [Double] {
    let vals = self
    let x = vals[0] * v[0] + vals[1] * v[1] + vals[2] * v[2] + vals[3]
    let y = vals[4] * v[0] + vals[5] * v[1] + vals[6] * v[2] + vals[7]
    let z = vals[8] * v[0] + vals[9] * v[1] + vals[10] * v[2] + vals[11]
    return [x, y, z]
}

func invertMatrix(_ input: inout [Double]) -> [Double] {
    var temp = Array(repeating: 0.0, count: 16)
    let tx = -input[3]
    let ty = -input[7]
    let tz = -input[11]

    for h in 0..<3 {
        for v in 0..<3 {
            temp[h + v * 4] = input[v + h * 4]
        }
    }

    for i in 0..<11 {
        input[i] = temp[i]
    }

    input[3] = tx * input[0] + ty * input[1] + tz * input[2]
    input[7] = tx * input[4] + ty * input[5] + tz * input[6]
    input[11] = tx * input[8] + ty * input[9] + tz * input[10]

    return input
}

class TriangleClass {
    var axis: Int = 0
    var normal: [Double] = []
    var nu: Double = 0.0
    var nv: Double = 0.0
    var nd: Double = 0.0
    var eu: Double = 0.0
    var ev: Double = 0.0
    var nu1: Double = 0.0
    var nv1: Double = 0.0
    var nu2: Double = 0.0
    var nv2: Double = 0.0
    var material: [Double] = []
    var shader: (([Double]) -> [Double]) = { pos in
        let x = ((pos[0]/32).truncatingRemainder(dividingBy: 2) + 2).truncatingRemainder(dividingBy: 2)
        let z = ((pos[2]/32 + 0.3).truncatingRemainder(dividingBy: 2) + 2).truncatingRemainder(dividingBy: 2)
        if ((x < 1) != (z < 1)) {
            return createVector(x: 0.4,y: 0.4,z: 0.4)
        } else {
            return createVector(x: 0.0,y: 0.4,z: 0.0)
        }
    }
    var isEmpty: Bool = false
    var hasShader: Bool = false
    init(p1: [Double], p2: [Double], p3: [Double], isEmpty: Bool = false) {
        self.isEmpty = isEmpty
        let edge1 = sub(p3, p1)
        let edge2 = sub(p2, p1)
        let normal = cross(edge1, edge2)
        if (abs(normal[0]) > abs(normal[1])) {
            if (abs(normal[0]) > abs(normal[2])) {
                self.axis = 0
            } else {
                self.axis = 2
            }
        } else {
            if (abs(normal[1]) > abs(normal[2])) {
                self.axis = 1
            } else {
                self.axis = 2
            }
        }
        let u = (self.axis + 1) % 3
        let v = (self.axis + 2) % 3
        let u1 = edge1[u]
        let v1 = edge1[v]
        
        let u2 = edge2[u]
        let v2 = edge2[v]
        self.normal = normalise(normal)
        self.nu = self.normal[u] / self.normal[self.axis]
        self.nv = self.normal[v] / self.normal[self.axis]
        self.nd = dot(self.normal, p1) / self.normal[self.axis]
        let det = u1 * v2 - v1 * u2
        self.eu = p1[u]
        self.ev = p1[v]
        self.nu1 = u1 / det
        self.nv1 = -v1 / det
        self.nu2 = v2 / det
        self.nv2 = -u2 / det
        self.material = [0.7, 0.7, 0.7]
    }
    func intersect(origin: [Double], dir: inout [Double], near: Double, far: Double) -> Double? {
        let u = (self.axis + 1) % 3
        let v = (self.axis + 2) % 3
        let d = dir[self.axis] + self.nu * dir[u] + self.nv * dir[v]
        let t = (self.nd - origin[self.axis] - self.nu * origin[u] - self.nv * origin[v]) / d
        if (t < near || t > far) {
            return nil
        }
        let Pu = origin[u] + t * dir[u] - self.eu
        let Pv = origin[v] + t * dir[v] - self.ev
        let a2 = Pv * self.nu1 + Pu * self.nv1
        if (a2 < 0) {
            return nil
        }
        let a3 = Pu * self.nu2 + Pv * self.nv2
        if (a3 < 0) {
            return nil
        }
        if ((a2 + a3) > 1) {
            return nil
        }
        return t
    }
}

class LightsClass {
    var arr: [Double] = []
    var colour: [Double] = []
    init() {}
}

var closest = TriangleClass(p1: [0,0,0], p2: [0,0,0], p3: [0,0,0], isEmpty: true)
class SceneClass {
    var triangles: [TriangleClass]
    var lights: [LightsClass] = Array(repeating:LightsClass() ,count : 3)
    var ambient: [Double] = [0, 0, 0]
    var background: [Double] = [0.8, 0.8, 1]

    init(triangles: [TriangleClass]) {
        self.triangles = triangles
    }

    func intersect(origin: [Double], dir: inout [Double], near: Double, far: Double) -> [Double] {
        var far = far

        for i in 0..<triangles.count {
            let triangle = triangles[i]
            if var d: Double? = triangle.intersect(origin: origin, dir:&dir, near: near, far: far) {
                if d == nil || d! > far || d! < near {
                    continue
                }
                far = d!
                closest = triangle
            }
        }

        if closest.isEmpty {
            return [background[0], background[1], background[2]]
        }
        
        var normal = closest.normal
        var hit = add(origin, scale(&dir, far))
        if dot(dir, normal) > 0 {
            normal = [-normal[0], -normal[1], -normal[2]]
        }

        var colour: [Double] = [];
        if (closest.hasShader) {
            colour = closest.shader(hit);
        } else {
            colour = closest.material;
        }

        var l = [self.ambient[0], self.ambient[1], self.ambient[2]]
        for i in 0..<lights.count {
            let light = lights[i]
            var toLight = sub(light.arr, hit)
            var distance = lengthVector(toLight)
            scaleVector(&toLight, 1.0/distance)
            distance -= 0.0001

            if blocked(O: hit, D: &toLight, far: distance) {
                continue
            }
            let nl = dot(normal, toLight)
            if nl > 0 {
                addVector(&l, scaleVector(&light.colour, nl))
            }
        }

        l = scalev(l, colour)
        return l
    }

    func blocked(O: [Double], D: inout [Double], far: Double) -> Bool {
        let near = 0.0001

        for i in 0..<triangles.count {
            let triangle = triangles[i]
            let d = triangle.intersect(origin: O, dir: &D, near: near, far: far)
            if d == nil || d! > far || d! < near {
                continue
            }
            return true
        }
        return false
    }
}

class RayClass {
    var origin: [Double] = []
    var dir: [Double] = []
    init() {}
}

class CameraClass {
    var origin: [Double] = []
    var directions: [[Double]] = Array(repeating: Array(repeating: 0.0, count: 3), count: 4)
    
    init(origin: [Double], lookat: inout [Double], up: [Double]) {
        var aaxis = subVector(&lookat,origin)
        let zaxis = normaliseVector(&aaxis)
        var baxis = cross(up, zaxis)
        let xaxis = normaliseVector(&baxis)
        var tmp = [0.0,0.0,0.0]
        var caxis = subVector(&tmp, zaxis)
        var daxis = cross(xaxis, caxis)
        let yaxis = normaliseVector(&daxis)
        var m = Array(repeating: 0.0, count: 16)
        m[0] = xaxis[0]; m[1] = xaxis[1]; m[2] = xaxis[2]
        m[4] = yaxis[0]; m[5] = yaxis[1]; m[6] = yaxis[2]
        m[8] = zaxis[0]; m[9] = zaxis[1]; m[10] = zaxis[2]
        invertMatrix(&m)
        m[3] = 0; m[7] = 0; m[11] = 0
        self.origin = origin
        self.directions[0] = normalise([-0.7, 0.7, 1])
        self.directions[1] = normalise([0.7, 0.7, 1])
        self.directions[2] = normalise([0.7, -0.7, 1])
        self.directions[3] = normalise([-0.7, -0.7, 1])
        self.directions[0] = transformMatrix(m, self.directions[0])
        self.directions[1] = transformMatrix(m, self.directions[1])
        self.directions[2] = transformMatrix(m, self.directions[2])
        self.directions[3] = transformMatrix(m, self.directions[3])
    }
    
    func generateRayPair(y: Double) -> [RayClass] {
        var rays: [RayClass] = Array(0..<2).map { _ in RayClass()  }
        rays[0].origin = self.origin
        rays[1].origin = self.origin
        var tmp1 = scale(&(self.directions[0]), y)
        var tmp2 = scale(&(self.directions[3]), 1 - y)
        rays[0].dir = addVector(&tmp1, tmp2)
        var tmp3 = scale(&(self.directions[1]), y)
        var tmp4 = scale(&(self.directions[2]), 1 - y)
        rays[1].dir = addVector(&tmp3, tmp4)
        return rays
    }
    
    func render(scene: SceneClass, pixels: inout [[[Double]]], width: Int, height: Int) {
        let cam = self
        renderRows(camera: cam, scene: scene, pixels: &pixels, width: width, height: height, starty: 0, stopy: height)
    }
}

func renderRows(camera: CameraClass, scene: SceneClass, pixels: inout [[[Double]]], width: Int, height: Int, starty: Int, stopy: Int) {
    for y in starty..<stopy {
        let rays = camera.generateRayPair(y: Double(y) / Double(height))
        for x in 0..<width {
            let xp = Double(x) / Double(width)
            var tmp1 : Array<Double> = scale(&rays[0].origin, xp)
            var tmp2 : Array<Double> = scale(&rays[1].origin, 1 - xp)
            let origin: [Double] = addVector(&tmp1, tmp2)
            var tmp3 : Array<Double> = scale(&rays[0].dir, xp)
            var tmp4 : Array<Double> = scale(&rays[1].dir, 1 - xp)
            var tmp5 : Array<Double> = addVector(&tmp3, tmp4)
            var dir = normaliseVector(&tmp5)
            let l = scene.intersect(origin: origin, dir: &dir, near: 0, far: 0)
            pixels[y][x] = l
        }
    }
}

func raytraceScene() -> [[[Double]]] {
    var triangles: [TriangleClass] = Array(repeating: TriangleClass(p1:createVector(x: 0, y: 0, z: 0), p2:createVector(x: 0, y: 0, z: 0), p3:createVector(x: 0, y: 0, z: 0)), count : 14)
    let tfl = createVector(x: -10, y: 10, z: -10)
    let tfr = createVector(x: 10, y: 10, z: -10)
    let tbl = createVector(x: -10, y: 10, z: 10)
    let tbr = createVector(x: 10, y: 10, z: 10)
    let bfl = createVector(x: -10, y: -10, z: -10)
    let bfr = createVector(x: 10, y: -10, z: -10)
    let bbl = createVector(x: -10, y: -10, z: 10)
    let bbr = createVector(x: 10, y: -10, z: 10)

    triangles[0] = TriangleClass(p1:tfl, p2:tfr, p3:bfr)
    triangles[1] = TriangleClass(p1:tfl, p2:bfr, p3:bfl)

    triangles[2] = TriangleClass(p1:tbl, p2:tbr, p3:bbr)
    triangles[3] = TriangleClass(p1:tbl, p2:bbr, p3:bbl)

    triangles[4] = TriangleClass(p1:tbl, p2:tfl, p3:bbl)
    triangles[5] = TriangleClass(p1:tfl, p2:bfl, p3:bbl)

    triangles[6] = TriangleClass(p1:tbr, p2:tfr, p3:bbr)
    triangles[7] = TriangleClass(p1:tfr, p2:bfr, p3:bbr)

    triangles[8] = TriangleClass(p1:tbl, p2:tbr, p3:tfr)
    triangles[9] = TriangleClass(p1:tbl, p2:tfr, p3:tfl)

    triangles[10] = TriangleClass(p1:bbl, p2:bbr, p3:bfr)
    triangles[11] = TriangleClass(p1:bbl, p2:bfr, p3:bfl)

    let ffl = createVector(x: -1000, y: -30, z: -1000)
    let ffr = createVector(x: 1000, y: -30, z: -1000)
    let fbl = createVector(x: -1000, y: -30, z: 1000)
    let fbr = createVector(x: 1000, y: -30, z: 1000)
    triangles[12] = TriangleClass(p1:fbl, p2:fbr, p3:ffr)
    triangles[12].hasShader = true
    triangles[13] = TriangleClass(p1:fbl, p2:ffr, p3:ffl)    
    triangles[13].hasShader = true

    var scene = SceneClass(triangles: triangles)
    scene.lights[0] = LightsClass()
    scene.lights[0].arr = createVector(x: 20, y: 38, z: -22)
    scene.lights[0].colour = createVector(x: 0.7, y: 0.3, z: 0.3)
    scene.lights[1] = LightsClass()
    scene.lights[1].arr = createVector(x: -23, y: 40, z: 17)
    scene.lights[1].colour = createVector(x: 0.7, y: 0.3, z: 0.3)
    scene.lights[2] = LightsClass()
    scene.lights[2].arr = createVector(x: 23, y: 20, z: 17)
    scene.lights[2].colour = createVector(x: 0.7, y: 0.7, z: 0.7)
    scene.ambient = createVector(x: 0.1, y: 0.1, z: 0.1)

    let size = 30
    var pixels = Array(repeating: Array(repeating: Array(repeating:0.0, count: 3), count: size), count: size)
    var add1 : Array<Double> = createVector(x: -40, y: 40, z: 40)
    var add2 : Array<Double> = createVector(x: 0, y: 0, z: 0)
    var add3 : Array<Double> = createVector(x: 0, y: 1, z: 0)
    var camera = CameraClass(origin: add1,
                            lookat: &add2,
                            up: add3)
    camera.render(scene: scene, pixels: &pixels, width: size, height: size)
    return pixels
}

func runThreeDRaytrace() -> Int {
    timer.start()
    raytraceScene()
    let time = timer.stop()
    print("Array Access - RunThreeDRaytrace:\t"+String(time)+"\tms");
    return  Int(time) 
}
_ = runThreeDRaytrace()
